#include <retro.h>
#include "retro.creator.h"

#define BROWSER_MAXLOOP     1000
#define BROWSER_ITEMFLAGS   TVIF_PARAM | TVIF_TEXT | \
                            TVIF_IMAGE | TVIF_SELECTEDIMAGE

#define BROWSER_DELETEFOLDER_MSG \
  _T("The folder is not empty. Delete its content?")

using namespace Retro::Creator;
using namespace Retro::Creator::Toolbox;
using namespace SPLib::Windows;
using namespace SPLib::Geom;

enum ResMenu
{
  ResMenuFirst = 0x0100,
  ResMenuOpen,
  ResMenuOpenAll,
  ResMenuOpenAllDescendants,
  ResMenuRename,
  ResMenuSave,
  ResMenuDelete,
  ResMenuAddFolder,
  ResMenuAddResource,
  ResMenuProperties,
};

enum ResChild
{
  ResChildFirst = 0x0200,
  ResChildList,
  ResChildToolbar,
};

enum ResToolbar
{
  ResToolbarFirst = 0x0300,
  ResToolbarDisplay,
  ResToolbarCollapseAll,
  ResToolbarExpandAll,
  ResToolbarNewFolder,
};

INT DisplayTable[][2] = {
//  INCLUDED FLAGS            EXCLUDED FLAGS
  { 0xFFFFFFFF,               RETRO_RESOURCE_DELETED  },
  { RETRO_RESOURCE_UNSAVED,   RETRO_RESOURCE_DELETED  },
  { RETRO_RESOURCE_DELETED,   0                       },
  { RETRO_RESOURCE_CORRUPTED, RETRO_RESOURCE_DELETED  },
  { 0xFFFFFFFF,               0                       }
};

#define Browser_IsDisplayed(resource) \
  ((resource->getFlags() & (DisplayTable[mDisplay][0])) > 0 && \
  (resource->getFlags() & RETRO_RESOURCE_INITIALIZED) > 0 && \
  !(resource->getFlags() & DisplayTable[mDisplay][1]))

INT CALLBACK CompareResource(
  LPARAM lParam1,
  LPARAM lParam2,
  LPARAM lParamSort
  )
{
  BrowserNode* wRes1 = (BrowserNode*)lParam1;
  BrowserNode* wRes2 = (BrowserNode*)lParam2;

  if (wRes1->type == BrowserNodeResource &&
    wRes2->type == BrowserNodeResource)
  {
    return _tcscmp(wRes1->resource->getName(), wRes2->resource->getName());
  }
  else if (wRes1->type == BrowserNodeFolder &&
    wRes2->type == BrowserNodeFolder)
  {
    return _tcscmp(wRes1->path->name, wRes2->path->name);
  }
  else
  {
    if (wRes1->type == BrowserNodeFolder)
      return -1;
    else
      return 1;
  }

  return -1;
}

Browser::Browser()
{
  mDisplay  = ResourceDisplayActive;
  mDragNode = NULL;
  mProject  = NULL;
}

Browser::~Browser()
{
  for (UINT i = 0; i < mNodes.getLength(); ++i)
    delete mNodes[i];
}

VOID  Browser::setDisplay(ResourceDisplay value)
{
  TCHAR szText[RETRO_MAXTEXT];

  mMenuDisplay.getItemText(value + 1, szText, RETRO_MAXTEXT);
  mToolbar.setItemText(szText, ResToolbarDisplay);

  mDisplay = value;
  updateList();
}

VOID  Browser::onCreate(Event::WndCreateEvent& iEvent)
{
  // create treeview
  {
    mTree.create(*this, WC_TREEVIEW, _T(""),
      WS_CHILD | WS_CLIPSIBLINGS | WS_TABSTOP | TVS_HASBUTTONS | TVS_INFOTIP |
      TVS_LINESATROOT | TVS_TRACKSELECT | TVS_EDITLABELS | TVS_SHOWSELALWAYS);
    mTree.setTheme(_T("explorer"));
    mTree.setID(ResChildList);

    listItem.itemex.mask        = TVIF_CHILDREN | TVIF_PARAM | TVIF_TEXT;
    listItem.itemex.pszText     = listItemText;
    listItem.itemex.cchTextMax  = ARRAYSIZE(listItemText);
    listItem.hInsertAfter       = TVI_SORT;
  }

  // create imagelist
  {
    HIMAGELIST wImageList = ImageList_Create(16, 16, ILC_COLOR32, 10, 10);

    mIconFile = ImageList_AddIcon(
      wImageList,
      (HICON)LoadImage(
      getInstance(),
      MAKEINTRESOURCE(IDI_FILE),
      IMAGE_ICON, 16, 16, 0));

    mIconFolder = ImageList_AddIcon(
      wImageList,
      (HICON)LoadImage(
      getInstance(),
      MAKEINTRESOURCE(IDI_FOLDER),
      IMAGE_ICON, 16, 16, 0));

    TreeView_SetImageList(mTree, wImageList, TVSIL_NORMAL);
  }

  // create display menu
  {
    mMenuDisplay.createPopup();
    mMenuDisplay.append(ResourceDisplayActive + 1,    _T("Active"));
    mMenuDisplay.append(ResourceDisplayUnsaved + 1,   _T("Unsaved"));
    mMenuDisplay.append(ResourceDisplayDeleted + 1,   _T("Deleted"));
    mMenuDisplay.append(ResourceDisplayCorrupted + 1, _T("Corrupted"));
    mMenuDisplay.append(ResourceDisplayAll + 1,       _T("All"));
  }

  // create toolbar
  {
    ToolItem items[] =
    {
      {ResToolbarCollapseAll, _T("-"),          ToolItemButton, NULL, 25    },
      {ResToolbarExpandAll,   _T("+"),          ToolItemButton, NULL, 25    },
      {ResToolbarNewFolder,   _T("Add Folder"), ToolItemButton              },
      {0,                     _T(""),           ToolItemSeparator           },
      {ResToolbarDisplay,     _T(""),           ToolItemMenu, mMenuDisplay, 105  },
    };

    mToolbar.create(*this);
    mToolbar.addStyle(WS_TABSTOP);
    mToolbar.addItems(items, ARRAYSIZE(items));
    mToolbar.setBackColor(0xFFFFFF);
    mToolbar.setID(ResChildToolbar);
  }

  // create treeview menus
  {
    mMenuResource.createPopup();
    mMenuResource.append(ResMenuOpen,                   _T("Open"));
    mMenuResource.append(ResMenuRename,                 _T("Rename"));
    mMenuResource.append(ResMenuAddFolder,              _T("Add To Folder"));
    mMenuResource.appendSeparator();
    mMenuResource.append(ResMenuSave,                   _T("Save"));
    mMenuResource.append(ResMenuDelete,                 _T(""));
    mMenuResource.appendSeparator();
    mMenuResource.append(ResMenuProperties,             _T("Properties..."));
    mMenuResource.setDefaultItem(ResMenuOpen);

    mMenuResourceType.createPopup();

    mMenuResourceType.append(ResMenuOpenAll,            _T("Open All"));
    mMenuResourceType.append(ResMenuOpenAllDescendants, _T("Open All Descendants"));
    mMenuResourceType.appendSeparator();
    mMenuResourceType.append(ResMenuAddFolder,          _T("Add Folder"));
    mMenuResourceType.append(ResMenuAddResource,        _T("Add Resource"));

    mMenuFolder.createPopup();
    mMenuFolder.append(ResMenuOpenAll,                  _T("Open All"));
    mMenuFolder.append(ResMenuOpenAllDescendants,       _T("Open All Descendants"));
    mMenuFolder.appendSeparator();
    mMenuFolder.append(ResMenuAddFolder,                _T("Add Folder"));
    mMenuFolder.append(ResMenuAddResource,              _T("Add Resource"));
    mMenuFolder.append(ResMenuRename,                   _T("Rename"));
    mMenuFolder.appendSeparator();
    mMenuFolder.append(ResMenuDelete,                   _T("Delete"));
  }

  // create layout
  {
    mLayout = getHandle();
    mLayout.addTop(TOOLBAR_HEIGHT, mToolbar)->margins.setAll(4);
    mLayout.addFill(mTree);
  }

  setDisplay(ResourceDisplayActive);
}

VOID  Browser::onDestroy()
{
  mLayout.destroy();
  mMenuResource.destroy();
  mMenuResourceType.destroy();
  mMenuFolder.destroy();
}

VOID  Browser::onSize(Event::SizeEvent& iEvent)
{
  mLayout.doLayout();
  ListView_SetColumnWidth(mTree, 1,
    iEvent.cx - mLayout.padding.getHoriz() - 50);
}

VOID  Browser::onCommand(Event::CommandEvent& iEvent)
{
  TVITEMEX wItem;

  wItem.mask   = TVIF_PARAM | TVIF_HANDLE;
  wItem.hItem  = TreeView_GetSelection(mTree);
  wItem.lParam = NULL;

  TreeView_GetItem(mTree, &wItem);

  BrowserNode* wNode = (BrowserNode*)wItem.lParam;

  if (wNode)
  {
    switch (iEvent.id)
    {
    case ResMenuOpen:
      openResource(wNode);
      break;
    case ResMenuOpenAll:
      openFolder(wNode, FALSE);
      break;
    case ResMenuOpenAllDescendants:
      openFolder(wNode, TRUE);
      break;
    case ResMenuRename:
      TreeView_EditLabel(mTree, TreeView_GetSelection(mTree));
      break;
    case ResMenuSave:
      if (wNode->type == BrowserNodeResource)
        Frame::get()->saveResource(wNode->resource);
      break;
    case ResMenuDelete:
      removeNode(wNode);
      break;
    case ResMenuProperties:
      if (wNode->type == BrowserNodeResource)
        Frame::get()->showProperties(wNode->resource);
      break;
    case ResMenuAddFolder:
      {
        BrowserNode* wFolder = addNewFolder(wNode);

        if (wNode->type == BrowserNodeResource)
        {
          moveNode(wNode, wFolder);
          TreeView_EditLabel(mTree, wFolder->treeItem);
        }
      }
      break;
    case ResMenuAddResource:
      Frame::get()->createResource(findRoot(wNode)->resourceType);
      break;
    }
  }
}

VOID  Browser::onNotify(Event::NotifyEvent& iEvent)
{
  switch (iEvent.data->idFrom)
  {
  case ResChildList:
    {
      Geom::Point wCursor = Geom::Point::fromCursor();
      TVHITTESTINFO wHitTest = { wCursor.toClient(mTree) };
      BrowserNode* wNode = NULL;

      switch (iEvent.data->code)
      {
      case NM_DBLCLK:
        if (wNode = getHoveredNode())
          openResource(wNode);
        break;
      case NM_RCLICK:
        if (wNode = getHoveredNode())
        {
          TreeView_SelectItem(mTree, TreeView_HitTest(mTree, &wHitTest));

          switch (wNode->type)
          {
          case BrowserNodeResourceType:
            TrackPopupMenu(mMenuResourceType, TPM_BOTTOMALIGN,
              wCursor.x, wCursor.y, 0, *this, NULL);
            break;
          case BrowserNodeFolder:
            TrackPopupMenu(mMenuFolder, TPM_BOTTOMALIGN,
              wCursor.x, wCursor.y, 0, *this, NULL);
            break;
          case BrowserNodeResource:
            {
              MENUITEMINFO info;

              info.cbSize     = sizeof(info);
              info.fMask      = MIIM_STRING;
              info.dwTypeData = wNode->resource->isDeleted() ?
                _T("Recover") : _T("Delete");

              SetMenuItemInfo(mMenuResource, ResMenuDelete, 0, &info);

              EnableMenuItem(mMenuResource, ResMenuSave,       MF_ENABLED);
              EnableMenuItem(mMenuResource, ResMenuProperties, MF_ENABLED);

              switch (wNode->resource->getType())
              {
              case Data::ResourceGridMap:
              case Data::ResourceSpriteMap:
              case Data::ResourceScript:
                EnableMenuItem(mMenuResource,
                  ResMenuOpen, MF_ENABLED);
                SetMenuDefaultItem(mMenuResource,
                  ResMenuOpen, MF_BYCOMMAND);
                break;
              default:
                EnableMenuItem(mMenuResource,
                  ResMenuOpen, MF_DISABLED | MF_GRAYED);
                SetMenuDefaultItem(mMenuResource,
                  ResMenuProperties, MF_BYCOMMAND);
                break;
              }

              TrackPopupMenu(mMenuResource, TPM_BOTTOMALIGN,
                wCursor.x, wCursor.y, 0, *this, NULL);
            }
            break;
          }
        }
        break;
      case TVN_BEGINLABELEDIT:
        if (wNode = (BrowserNode*)((NMTVDISPINFO*)iEvent.data)->item.lParam)
        {
          switch (wNode->type)
          {
          case BrowserNodeResource:
            SetWindowText(TreeView_GetEditControl(mTree),
              wNode->resource->getName());
            break;
          case BrowserNodeFolder:
            SetWindowText(TreeView_GetEditControl(mTree),
              wNode->path->name);
            break;
          default:
            return;
          }
        }
        break;
      case TVN_ENDLABELEDIT:
        {
          TVITEM &newItem = ((NMTVDISPINFO*)iEvent.data)->item;

          if ((wNode = (BrowserNode*)newItem.lParam) && newItem.pszText)
          {
            switch (wNode->type)
            {
            case BrowserNodeResource:
              if (_tcscmp(wNode->resource->getName(), newItem.pszText) != 0)
                Frame::get()->renameResource(wNode->resource, newItem.pszText);

              getResourceText(wNode->resource);
              break;
            case BrowserNodeFolder:
              delete[] wNode->path->name;

              wNode->path->name = new TCHAR[_tcslen(newItem.pszText) + 1];
              _tcscpy(wNode->path->name, newItem.pszText);
              _tcscpy_s(listItemText, wNode->path->name);
              break;
            default:
              return;
            }

            newItem.mask        = TVIF_TEXT | TVIF_HANDLE;
            newItem.pszText     = listItemText;
            newItem.cchTextMax  = RETRO_MAXTEXT;
            TreeView_SetItem(mTree, &newItem);

            sortList();
          }
        }
        break;
      case TVN_KEYDOWN:
        if (wNode = getSelectedNode())
        {
          switch (((NMTVKEYDOWN*)iEvent.data)->wVKey)
          {
          case VK_DELETE:
            removeNode(wNode);
            break;
          case VK_F2:
            TreeView_EditLabel(mTree, TreeView_GetSelection(mTree));
            break;
          case VK_SPACE:
          case VK_RETURN:
            openResource(wNode);
            break;
          }
        }
        break;
      case TVN_BEGINDRAG:
        wNode = (BrowserNode*)((LPNMTREEVIEW)iEvent.data)->itemNew.lParam;

        if (wNode)
        {
          switch (wNode->type)
          {
          case BrowserNodeFolder:
          case BrowserNodeResource:
            beginDrag(wNode);
            break;
          }
        }
        break;
      case TVN_ITEMEXPANDED:
        wNode = (BrowserNode*)((LPNMTREEVIEW)iEvent.data)->itemNew.lParam;

        if (wNode && wNode->type == BrowserNodeFolder)
        {
          if (((LPNMTREEVIEW)iEvent.data)->action & TVE_EXPAND)
          {
            wNode->path->flags |= RETRO_HIERARCHY_EXPANDED;
          }
          else
          {
            wNode->path->flags &= ~RETRO_HIERARCHY_EXPANDED;
          }
        }
        break;
      case TVN_GETINFOTIP:
        wNode = (BrowserNode*)((NMTVGETINFOTIP*)iEvent.data)->lParam;

        if (wNode && wNode->type == BrowserNodeResource)
        {
          NMTVGETINFOTIP* wTipInfo = (NMTVGETINFOTIP*)iEvent.data;

          switch (wNode->resource->getType())
          {
          case Data::ResourceGridMap:
          case Data::ResourceSpriteMap:
            _stprintf_s(
              listItemText,
              _T("%.4d - %s [%d x %d x %d]"),
              wNode->resource->getID(),
              wNode->resource->getName(),
              ((Data::Map*)wNode->resource)->getWidth(),
              ((Data::Map*)wNode->resource)->getHeight(),
              ((Data::Map*)wNode->resource)->getLayers());
            break;
          case Data::ResourceScript:
          case Data::ResourceTexture:
            _stprintf_s(
              listItemText,
              _T("%.4d - %s"),
              wNode->resource->getID(),
              wNode->resource->getName());
            break;
          }

          wTipInfo->cchTextMax  = ARRAYSIZE(listItemText);
          wTipInfo->pszText     = listItemText;
        }
        break;
      }
    }
    break;
  case ResChildToolbar:
    switch (((ToolBarNotify*)iEvent.data)->itemID)
    {
    case ResToolbarDisplay:
      setDisplay((ResourceDisplay)(((ToolBarNotify*)iEvent.data)->value - 1));
      break;
    case ResToolbarCollapseAll:
    case ResToolbarExpandAll:
      for (UINT i = 0; i < mNodes.getLength(); ++i)
      {
        switch (mNodes[i]->type)
        {
        case BrowserNodeFolder:
        case BrowserNodeResourceType:
          TreeView_Expand(
            mTree,
            mNodes[i]->treeItem,
            ((ToolBarNotify*)iEvent.data)->itemID == ResToolbarExpandAll ?
              TVE_EXPAND : TVE_COLLAPSE);
          break;
        }
      }

      mTree.invalidate();
      break;
    case ResToolbarNewFolder:
      addNewFolder(getSelectedNode());
      break;
    }
  }
}

VOID  Browser::onFocus(Event::FocusEvent& iEvent)
{
  if (iEvent.focused)
    mTree.focus();
}

VOID  Browser::onMouseMove(Event::MouseEvent& iEvent)
{
  if (mDragNode)
  {
    TVHITTESTINFO wHitTest;
    TVITEMEX wHitItem;

    wHitTest.pt = Geom::Point::fromCursor();
    mTree.pointToClient(wHitTest.pt);

    if (wHitItem.hItem = TreeView_HitTest(mTree, &wHitTest))
    {
      wHitItem.mask   = TVIF_PARAM | TVIF_HANDLE;
      wHitItem.lParam = NULL;

      TreeView_GetItem(mTree, &wHitItem);

      BrowserNode* wNode = (BrowserNode*)wHitItem.lParam;

      if (wNode != mDragNode &&
        findRoot(wNode)->resourceType == findRoot(mDragNode)->resourceType)
      {
        switch (wNode->type)
        {
        case BrowserNodeFolder:
        case BrowserNodeResourceType:
          TreeView_SelectDropTarget(
            mTree,
            wHitItem.hItem);

          SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
          break;
        case BrowserNodeResource:
          TreeView_SelectDropTarget(
            mTree,
            TreeView_GetParent(mTree, wHitItem.hItem));

          SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
          break;
        default:
          wHitItem.hItem = NULL;
        }
      }
    }

    if (!wHitItem.hItem)
    {
      TreeView_SelectDropTarget(mTree, NULL);
      SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_NO)));
    }
  }
}

VOID  Browser::onMouseUp(Event::MouseEvent& iEvent)
{
  if (mDragNode)
  {
    TVITEMEX wHitItem;

    if (wHitItem.hItem = TreeView_GetDropHilight(mTree))
    {
      wHitItem.mask   = TVIF_PARAM | TVIF_HANDLE;
      wHitItem.lParam = NULL;

      TreeView_GetItem(mTree, &wHitItem);
      moveNode(mDragNode, (BrowserNode*)wHitItem.lParam);
    }

    TreeView_SelectDropTarget(mTree, NULL);

    ReleaseCapture();
    SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));

    mDragNode = NULL;
  }
}

VOID  Browser::beginDrag(BrowserNode* iNode)
{
  TreeView_SelectItem(mTree, iNode->treeItem);

  SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
  SetCapture(*this);

  mDragNode = iNode;
}

VOID  Browser::setProject(Data::RetroProject* iProject)
{
  TreeView_DeleteAllItems(mTree);

  for (UINT i = 0; i < mNodes.getLength(); ++i)
    delete mNodes[i];

  mNodes.clear();
  mNodes.setSize(100);

  if (mProject = iProject)
  {
    const Generic::Vector<Data::Resource*>& wResources =
      Frame::get()->getProject().getResources();

    listItem.hParent = TVI_ROOT;

    for (int i = (int)Data::ResourceFirst; i <= (int)Data::ResourceLast; ++i)
    {
      BrowserNode* wNode = new BrowserNode;

      wNode->type = BrowserNodeResourceType;
      wNode->resourceType = (Data::ResourceType)i;

      initItem(wNode);

      wNode->treeItem = listRoots[i] = TreeView_InsertItem(mTree, &listItem);
      mNodes.add(wNode);
    }

    Generic::Vector<HierarchyPath*, 10> wPathsToAdd;
    Hierarchy* wHierarchy = Frame::get()->getHierarchy();
    INT wPathLoops = 0;

    wPathsToAdd.setSize(wHierarchy->getPaths().getLength());

    for (UINT i = 0; i < wHierarchy->getPaths().getLength(); ++i)
      wPathsToAdd.add(wHierarchy->getPaths()[i]);

    while (wPathsToAdd.getLength() > 0)
    {
      HierarchyPath* wPath = wPathsToAdd[0];

      wPathsToAdd.remove(0);

      if (!addFolder(wPath))
      {
        if (++wPathLoops == BROWSER_MAXLOOP)
          break;

        wPathsToAdd.add(wPath);
      }
    }

    for (UINT i = 0; i < wResources.getLength(); ++i)
    {
      if (Browser_IsDisplayed(wResources[i]))
        addResource(wResources[i]);
    }

    for (int i = (int)Data::ResourceFirst; i <= (int)Data::ResourceLast; ++i)
      TreeView_Expand(mTree, listRoots[i], TVE_EXPAND);

    for (UINT i = 0; i < mNodes.getLength(); ++i)
    {
      if (mNodes[i]->type == BrowserNodeFolder &&
        mNodes[i]->path->flags & RETRO_HIERARCHY_EXPANDED)
      {
        TreeView_Expand(mTree, mNodes[i]->treeItem, TVE_EXPAND);
      }
    }

    sortList();
  }
}

BrowserNode* Browser::addNewFolder(const BrowserNode* iParent)
{
  if (iParent->type == BrowserNodeResource)
  {
    TVITEMEX item;

    item.mask   = TVIF_PARAM | TVIF_HANDLE;
    item.lParam = NULL;
    item.hItem  = TreeView_GetParent(mTree, iParent->treeItem);

    TreeView_GetItem(mTree, &item);

    iParent = (BrowserNode*)item.lParam;
  }

  BrowserNode* wNode = addFolder(
    Frame::get()->getHierarchy()->addPath(
    _T("New Folder"), iParent->type == BrowserNodeFolder ?
    iParent->path->pathID : -iParent->resourceType));

  sortList();
  TreeView_EditLabel(mTree, wNode->treeItem);
  sortList();
  TreeView_SelectItem(mTree, wNode->treeItem);

  return wNode;
}

BrowserNode* Browser::addFolder(HierarchyPath* iValue)
{
  if (iValue->parentID >= 0)
  {
    BrowserNode* wParent = findFolder(iValue->parentID);

    if (!wParent)
      return NULL;

    listItem.hParent = wParent->treeItem;
  }
  else
  {
    listItem.hParent = listRoots[(-iValue->parentID) - Data::ResourceFirst];
  }

  BrowserNode* wNode = new BrowserNode;

  wNode->type     = BrowserNodeFolder;
  wNode->path     = iValue;

  initItem(wNode);

  wNode->treeItem = TreeView_InsertItem(mTree, &listItem);

  TreeView_Expand(mTree, listItem.hParent, TVE_EXPAND);

  mNodes.add(wNode);
  return wNode;
}

const BrowserNode* Browser::addResource(Data::Resource* value)
{
  const HierarchyPath* wPath =
    Frame::get()->getHierarchy()->getItemPath(
    value->getID(), value->getType());

  BrowserNode* wNode = new BrowserNode;

  wNode->type      = BrowserNodeResource;
  wNode->resource  = value;

  initItem(wNode);
  listItem.hParent = listRoots[(int)value->getType()];

  if (wPath)
  {
    BrowserNode* wParent = findFolder(wPath->pathID);

    listItem.hParent = wParent ?
      wParent->treeItem :
    listRoots[(int)value->getType()];
  }

  wNode->treeItem = TreeView_InsertItem(mTree, &listItem);

  mNodes.add(wNode);
  return wNode;
}

VOID  Browser::moveNode(
  BrowserNode* iNode,
  const BrowserNode* iParent
  )
{
  switch (iNode->type)
  {
  case BrowserNodeFolder:
    switch (iParent->type)
    {
    case BrowserNodeFolder:
      iNode->path->parentID = iParent->path->pathID;
      break;
    case BrowserNodeResourceType:
      iNode->path->parentID = -(INT)iParent->resourceType;
      break;
    }

    clearFolder(iNode, NULL);
    break;
  case BrowserNodeResource:
    switch (iParent->type)
    {
    case BrowserNodeFolder:
      Frame::get()->getHierarchy()->setItemPath(
        iNode->resource->getID(),
        iParent->path->pathID,
        iNode->resource->getType());
      break;
    case BrowserNodeResourceType:
      Frame::get()->getHierarchy()->unsetItemPath(
        iNode->resource->getID(),
        iNode->resource->getType());
      break;
    }
    break;
  }

  TreeView_DeleteItem(mTree, iNode->treeItem);

  listItem.hParent = iParent->treeItem;
  initItem(iNode);

  iNode->treeItem = TreeView_InsertItem(mTree, &listItem);

  TreeView_Expand(mTree, listItem.hParent, TVE_EXPAND);
  TreeView_SelectItem(mTree, iNode->treeItem);

  updateList();
}

VOID  Browser::removeNode(BrowserNode* iNode)
{
  switch (iNode->type)
  {
  case BrowserNodeFolder:
    switch (TreeView_GetChild(mTree, iNode->treeItem) == 0 ? IDNO :
      MessageBox(NULL, BROWSER_DELETEFOLDER_MSG,
      _T("Delete"), MB_ICONQUESTION | MB_YESNOCANCEL))
    {
    case IDYES:
      deleteFolder(iNode);
      break;
    case IDNO:
      clearFolder(iNode, findFolder(iNode->path->parentID));
      break;
    case IDCANCEL:
      return;
    }

    mNodes.remove(mNodes.getIndex(iNode));
    Frame::get()->getHierarchy()->removePath(iNode->path->pathID);

    TreeView_DeleteItem(mTree, iNode->treeItem);
    delete iNode;

    updateList();
    break;
  case BrowserNodeResource:
    if (iNode->resource->isDeleted())
      Frame::get()->recoverResource(iNode->resource);
    else
      Frame::get()->deleteResource(iNode->resource);
    break;
  }
}

VOID  Browser::clearFolder(
  BrowserNode* iFolder,
  BrowserNode* iNewParent
  )
{
  for (UINT i = 0; i < mNodes.getLength(); ++i)
  {
    if (TreeView_GetParent(mTree, mNodes[i]->treeItem) != iFolder->treeItem)
      continue;

    switch (mNodes[i]->type)
    {
    case BrowserNodeFolder:
      if (iNewParent)
      {
        mNodes[i]->path->parentID = iNewParent->path->pathID;
      }
      else
      {
        clearFolder(mNodes[i], iNewParent);
        Frame::get()->getHierarchy()->removePath(mNodes[i]->path->pathID);
      }
      break;
    case BrowserNodeResource:
      if (iNewParent)
      {
        if (iNewParent->type == BrowserNodeResourceType)
        {
          Frame::get()->getHierarchy()->unsetItemPath(
            mNodes[i]->resource->getID(),
            mNodes[i]->resource->getType());
        }
        else
        {
          Frame::get()->getHierarchy()->setItemPath(
            mNodes[i]->resource->getID(),
            iNewParent->path->pathID,
            mNodes[i]->resource->getType());
        }
      }
      break;
    }

    if (iNewParent)
    {
      initItem(mNodes[i]);
      listItem.hParent = iNewParent->treeItem;

      mNodes[i]->treeItem = TreeView_InsertItem(mTree, &listItem);
    }
    else
    {
      delete mNodes[i];
      mNodes.remove(i--);
    }
  }
}

VOID  Browser::deleteFolder(
  BrowserNode* iFolder
  )
{
  for (UINT i = 0; i < mNodes.getLength(); ++i)
  {
    if (TreeView_GetParent(mTree, mNodes[i]->treeItem) != iFolder->treeItem)
      continue;

    switch (mNodes[i]->type)
    {
    case BrowserNodeFolder:
      deleteFolder(mNodes[i]);
      Frame::get()->getHierarchy()->removePath(mNodes[i]->path->pathID);
      break;
    case BrowserNodeResource:
      Frame::get()->getHierarchy()->unsetItemPath(
          mNodes[i]->resource->getID(),
          mNodes[i]->resource->getType());
      mNodes[i]->resource->setDeleted();
      break;
    }

    delete mNodes[i];
    mNodes.remove(i--);
  }
}

VOID  Browser::updateList()
{
  if (!mProject) return;

  const Generic::Vector<Data::Resource*>& wResources =
    Frame::get()->getProject().getResources();

  for (UINT i = 0; i < wResources.getLength(); ++i)
  {
    if (Browser_IsDisplayed(wResources[i]) &&
        !findResource(wResources[i]))
    {
      addResource(wResources[i]);
    }
  }

  TVITEMEX item;

  item.pszText    = listItemText;
  item.cchTextMax = RETRO_MAXTEXT;

  for (UINT i = 0; i < mNodes.getLength(); ++i)
  {
    if (mNodes[i]->type == BrowserNodeResource)
    {
      if (Browser_IsDisplayed(mNodes[i]->resource))
      {
        getResourceText(mNodes[i]->resource);

        item.hItem  = mNodes[i]->treeItem;
        item.mask   = TVIF_TEXT | TVIF_HANDLE;
        TreeView_SetItem(mTree, &item);
      }
      else
      {
        TreeView_DeleteItem(mTree, mNodes[i]->treeItem);

        delete mNodes[i];
        mNodes.remove(i--);
      }
    }
  }

  sortList();
  mTree.invalidate();
}

VOID  Browser::getResourceText(Data::Resource* value)
{
  PTSTR wStatus;

  if (value->isDeleted())
    wStatus = _T(" (deleted)");
  else if (value->isUnsaved())
    wStatus = _T(" (unsaved)");
  else if (value->isCorrupted())
    wStatus = _T(" (corrupted)");
  else
    wStatus = _T("");

  _stprintf_s(
    listItemText,
    _T("%s%s"),
    value->getName(),
    wStatus);
}

VOID  Browser::initItem(BrowserNode* iNode)
{
  switch (iNode->type)
  {
  case BrowserNodeResource:
    getResourceText(iNode->resource);
    listItem.itemex.iImage = mIconFile;
    break;
  case BrowserNodeFolder:
    _tcscpy_s(listItemText, iNode->path->name);
    listItem.itemex.iImage = mIconFolder;
    break;
  case BrowserNodeResourceType:
    _tcscpy(listItemText, GetResourceName(iNode->resourceType));
    listItem.itemex.iImage = mIconFolder;
    break;
  }

  listItem.itemex.lParam  = (LPARAM)iNode;
  listItem.itemex.mask    = BROWSER_ITEMFLAGS;
  listItem.itemex.iSelectedImage = listItem.itemex.iImage;
}

VOID  Browser::sortList()
{
  TVSORTCB wSortInfo;

  wSortInfo.lpfnCompare = CompareResource;
  wSortInfo.lParam = NULL;

  for (UINT i = 0; i < mNodes.getLength(); ++i)
  {
    switch (mNodes[i]->type)
    {
    case BrowserNodeFolder:
    case BrowserNodeResourceType:

      wSortInfo.hParent = mNodes[i]->treeItem;
      TreeView_SortChildrenCB(mTree, &wSortInfo, 0);
      break;
    }
  }
}

VOID  Browser::selectResource(Data::Resource* iValue)
{
  BrowserNode* wNode = findResource(iValue);

  if (wNode)
  {
    TreeView_EnsureVisible(mTree, wNode->treeItem);
    TreeView_SelectItem(mTree, wNode->treeItem);
  }
}

BrowserNode* Browser::findResource(Data::Resource* iValue)
{
  for (UINT i = 0; i < mNodes.getLength(); ++i)
  {
    if (mNodes[i]->type == BrowserNodeResource)
    {
      if (mNodes[i]->resource == iValue)
        return mNodes[i];
    }
  }

  return NULL;
}

BrowserNode* Browser::findFolder(INT iID)
{
  for (UINT i = 0; i < mNodes.getLength(); ++i)
  {
    switch (mNodes[i]->type)
    {
    case BrowserNodeFolder:
      if (mNodes[i]->path->pathID == iID)
        return mNodes[i];
      break;
    case BrowserNodeResourceType:
      if (mNodes[i]->resourceType == (Data::ResourceType)-iID)
        return mNodes[i];
      break;
    }
  }

  return NULL;
}

BrowserNode* Browser::findRoot(BrowserNode* iNode)
{
  HTREEITEM wParent = iNode->treeItem;
  TVITEMEX wRoot;

  wRoot.hItem = NULL;

  while (wParent = TreeView_GetParent(mTree, wParent))
    wRoot.hItem = wParent;

  if (wRoot.hItem)
  {
    wRoot.mask   = TVIF_PARAM | TVIF_HANDLE;
    wRoot.lParam = NULL;

    TreeView_GetItem(mTree, &wRoot);

    return (BrowserNode*)wRoot.lParam;
  }

  return iNode;
}

BrowserNode* Browser::getSelectedNode()
{
  TVITEMEX item;

  item.mask   = TVIF_PARAM | TVIF_HANDLE;
  item.hItem  = TreeView_GetSelection(mTree);
  item.lParam = NULL;

  if (!item.hItem)
    return NULL;

  TreeView_GetItem(mTree, &item);

  return (BrowserNode*)item.lParam;
}

BrowserNode* Browser::getHoveredNode()
{
  Geom::Point wCursorPos = Geom::Point::fromCursor();

  TVITEMEX item;
  TVHITTESTINFO wHitTest;

  wHitTest.pt = wCursorPos;
  ScreenToClient(mTree, &wHitTest.pt);

  item.mask   = TVIF_PARAM | TVIF_HANDLE;
  item.hItem  = TreeView_HitTest(mTree, &wHitTest);
  item.lParam = NULL;

  TreeView_GetItem(mTree, &item);

  return (BrowserNode*)item.lParam;
}

VOID  Browser::openResource(BrowserNode* iNode)
{
  if (iNode->type != BrowserNodeResource)
    return;

  switch (iNode->resource->getType())
  {
  case Data::ResourceGridMap:
  case Data::ResourceSpriteMap:
  case Data::ResourceScript:
    Frame::get()->editResource(iNode->resource);
    break;
  default:
    Frame::get()->showProperties(iNode->resource);
    break;
  }
}

VOID  Browser::openFolder(BrowserNode* iFolder, BOOL iDescendants)
{
  for (UINT i = 0; i < mNodes.getLength(); ++i)
  {
    if (TreeView_GetParent(mTree, mNodes[i]->treeItem) != iFolder->treeItem)
      continue;

    switch (mNodes[i]->type)
    {
    case BrowserNodeFolder:
      if (iDescendants)
        openFolder(mNodes[i], TRUE);
      break;
    case BrowserNodeResource:
      openResource(mNodes[i]);
      break;
    }
  }
}
